package de.yvert.jingle.impl.reader;

import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import de.yvert.jingle.Image2D;
import de.yvert.jingle.ImageReader;
import de.yvert.jingle.hdr.HdrImage2D;
import de.yvert.jingle.ldr.LdrImage2D;

public class ImageReader_ppm implements ImageReader
{

private static final String INT_STR = "[0-9]+";
	
// PPM = <PPM-ID> <WIDTH> <HEIGHT> <MAX_VALUE> <rgb-data> 
private static final Pattern PPM_HEADER = 
	Pattern.compile("P6" + "\\s+" 
					+ "("+INT_STR+")" + "\\s+" 
					+ "("+INT_STR+")" + "\\s+"
					+ "("+INT_STR+")" + "\\s");

private static class CharSeq implements CharSequence
{

private int off, length;
private byte[] data;

public CharSeq(InputStream in) throws IOException
{
	int offset = 0;
	byte[] b = new byte[1024];
	
	while (true)
	{
		int i = in.read(b, offset, b.length-offset);
		if (i <= 0) break;
			
		offset += i;
		if (offset == b.length)
		{
			byte[] tmp = new byte[b.length*2];
			for (int j = 0; j < b.length; j++)
				tmp[j] = b[j];
			b = tmp;
		}
	}
	
	off = 0;
	data = b;
	length = offset;
}

private CharSeq(byte[] data, int off, int length)
{
	this.data = data;
	this.off = off;
	this.length = length;
}

public byte byteAt(int index)
{
	if (index < 0 || index > length-1)
		throw new IndexOutOfBoundsException();
	return data[off+index]; 
}

// @Implements CharSequence
public char charAt(int index) 
{
	return (char) byteAt(index); 
}

// @Implemtents CharSequence
public int length() 
{
	return length;
}

// @Implements CharSequence
public CharSequence subSequence(int start, int end) 
{
	if (start < 0 || end < 0 || end > length || start > end)
		throw new IndexOutOfBoundsException();
	return new CharSeq(data, off+start, end-start);
}

@Override
public String toString()
{
	return new String(data, off, length);	
}
	
}
	


public Image2D load(InputStream in) throws IOException 
{
	CharSeq cs = new CharSeq(new BufferedInputStream(in));
	
	Matcher m = PPM_HEADER.matcher(cs);
	if (!m.lookingAt())
		throw new IOException("Invalid ppm file!");
	
	int xres = Integer.parseInt(m.group(1));
	int yres = Integer.parseInt(m.group(2));
	int max = Integer.parseInt(m.group(3));
	
	if (xres < 0 || yres < 0)
		throw new IOException("Invalid ppm file!");
	if (max < 0 || max > 65536)
		throw new IOException("Invalid ppm file!");
	
	// Though 8bit are common, 16bit are also possible
	if (max < 256)
		return load8Bit(xres, yres, max, cs, m.end());
	else 
		return load16Bit(xres, yres, max, cs, m.end());
}

private Image2D load8Bit(int xres, int yres, int max, CharSeq cs, int start) throws IOException 
{
	if (xres*yres*3 > cs.length()-start)
		throw new IOException("Invalid ppm file!");
	
	float scale = 255f/max;
	
	byte[] imgData = new byte[xres*yres*4];
	for (int y = 0; y < yres; y++)
	{	
		for (int x = 0; x < xres; x++)
		{
			int offs = y*xres+x;
			imgData[offs*4+0] = (byte) (cs.byteAt(start+offs*3+0)*scale);  
			imgData[offs*4+1] = (byte) (cs.byteAt(start+offs*3+1)*scale);  
			imgData[offs*4+2] = (byte) (cs.byteAt(start+offs*3+2)*scale);  
			imgData[offs*4+3] = 0;  
		}
	}	
	
	return new LdrImage2D(xres, yres, imgData);
}

// TODO: Not tested yet, as I don't have highres ppm files.
private Image2D load16Bit(int xres, int yres, int max, CharSeq cs, int start) throws IOException 
{
	if (xres*yres*3*2 > cs.length()-start)
		throw new IOException("Invalid ppm file!");

	float scale = 1f/max;
	
	float[] imgData = new float[xres*yres*4];
	for (int y = 0; y < yres; y++)
	{	
		for (int x = 0; x < xres; x++)
		{
			int offs = y*xres+x;
			imgData[offs*4+0] = ((cs.byteAt(start+offs*6+0)<<8) | cs.byteAt(start+offs*6+1))*scale;  
			imgData[offs*4+1] = ((cs.byteAt(start+offs*6+2)<<8) | cs.byteAt(start+offs*6+3))*scale;  
			imgData[offs*4+2] = ((cs.byteAt(start+offs*6+4)<<8) | cs.byteAt(start+offs*6+5))*scale;  
			imgData[offs*4+3] = 0;  
		}
	}	
	
	return new HdrImage2D(xres, yres, imgData);
}

}
